package readini

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"strings"
	"unicode"
)

const (
	// DefaultSection is the name of default section. You can use this constant or the string literal.
	// In most of cases, an empty string is all you need to access the section.
	DefaultSection = "DEFAULT"

	// KeyValueDelimiter is the delimiter used to separate key and value.
	KeyValueDelimiter = "="
)

var (
	// CommentSign is the sign of the comment line.
	// This variable will be changed to ";" automatically on Windows at package init time.
	CommentSign = "#"

	// LineBreak is the delimiter to determine or compose a new line.
	// This variable will be changed to "\r\n" automatically on Windows at package init time.
	LineBreak = "\n"
)

func parse(cfg *Configuration, reader *bufio.Reader) error {
	isEOF := false
	sectionName := DefaultSection
	for !isEOF {
		line, err := readUntil(reader, &isEOF)
		if err != nil {
			return err
		}
		if len(line) == 0 {
			continue
		}

		if line[0] == CommentSign[0] {
			continue
		}

		if line[0] == '[' {
			sectionName, err = parseSectionName(string(line), cfg)
			if err != nil {
				return err
			}
			continue
		}

		if len(cfg.SectionList) == 0 {
			err = cfg.newSection(sectionName)
			if err != nil {
				return err
			}
		}

		key, offset, err := parseKey(string(line))
		if err != nil {
			return err
		}

		value, err := parseValue(string(line[offset:]))
		if err != nil {
			return err
		}

		err = cfg.Sections[sectionName].newValue(key, value)
		if err != nil {
			return err
		}
	}
	return nil
}

func readUntil(buf *bufio.Reader, isEOF *bool) ([]byte, error) {
	data, err := buf.ReadBytes('\n')
	if err != nil {
		if err == io.EOF {
			*isEOF = true
		} else {
			return nil, err
		}
	}
	data = bytes.TrimLeftFunc(data, unicode.IsSpace)
	return data, nil
}

func parseSectionName(line string, cfg *Configuration) (string, error) {
	closeIndex := strings.IndexAny(line, "]")
	if closeIndex == -1 {
		return "", fmt.Errorf("Unclosed section: %s", line)
	}
	sectionName := line[1:closeIndex]
	err := cfg.newSection(sectionName)
	if err != nil {
		return "", err
	}
	return sectionName, nil
}

func parseKey(line string) (string, int, error) {
	endIndex := strings.IndexAny(line, KeyValueDelimiter)
	if endIndex == -1 {
		return "", -1, fmt.Errorf("Delimiter(%s) not found", KeyValueDelimiter)
	}
	return strings.TrimSpace(line[0:endIndex]), endIndex + 1, nil
}

func parseValue(line string) (string, error) {
	line = strings.TrimSpace(line)
	if len(line) == 0 {
		return "", nil
	}

	line = cleanComment(line)
	return line, nil
}

func cleanComment(in string) string {
	i := strings.IndexAny(in, "#;")
	if i == -1 {
		return in
	}
	return in[:i]
}
